package cmd

import (
	"context"
	"fmt"
	"io"
	"strings"

	"github.com/aquasecurity/starboard/pkg/apis/aquasecurity/v1alpha1"
	"github.com/aquasecurity/starboard/pkg/kube"
	"github.com/aquasecurity/starboard/pkg/starboard"
	"github.com/aquasecurity/starboard/pkg/vulnerabilityreport"
	"github.com/spf13/cobra"
	"k8s.io/cli-runtime/pkg/genericclioptions"
	"k8s.io/cli-runtime/pkg/printers"
	"sigs.k8s.io/controller-runtime/pkg/client"
)

func NewGetVulnerabilityReportsCmd(executable string, cf *genericclioptions.ConfigFlags, out io.Writer) *cobra.Command {
	cmd := &cobra.Command{
		Use:     "vulnerabilityreports (NAME | TYPE/NAME)",
		Aliases: []string{"vulns", "vuln", "vulnerabilities"},
		Short:   "Get vulnerability reports",
		Long: `Get vulnerability reports for the specified workload

TYPE is a Kubernetes workload. Shortcuts and API groups will be resolved, e.g. 'po' or 'deployments.apps'.
NAME is the name of a particular Kubernetes workload.
`,
		Example: fmt.Sprintf(`  # Get vulnerability reports for a Deployment with the specified name
  %[1]s get vulnerabilityreports deploy/nginx

  # Get vulnerability reports for a Deployment with the specified name in the specified namespace
  %[1]s get vulnerabilities deploy/nginx -n staging

  # Get vulnerability reports for a ReplicaSet with the specified name
  %[1]s get vulns replicaset/nginx

  # Get vulnerability reports for the specified container belonging to
  # a ReplicaSet with the specified name
  %[1]s get vulns replicaset/nginx --container nginx

  # Get vulnerability reports for a CronJob with the specified name in JSON output format
  %[1]s get vuln cj/my-job -o json`, executable),
		RunE: func(cmd *cobra.Command, args []string) error {
			ctx := context.Background()

			kubeConfig, err := cf.ToRESTConfig()
			if err != nil {
				return err
			}
			scheme := starboard.NewScheme()
			kubeClient, err := client.New(kubeConfig, client.Options{Scheme: scheme})
			if err != nil {
				return err
			}
			ns, _, err := cf.ToRawKubeConfigLoader().Namespace()
			if err != nil {
				return err
			}
			mapper, err := cf.ToRESTMapper()
			if err != nil {
				return err
			}
			workload, _, err := WorkloadFromArgs(mapper, ns, args)
			if err != nil {
				return err
			}
			cm, err := kube.InitCompatibleMgr(kubeClient.RESTMapper())
			if err != nil {
				return err
			}
			objectResolver := kube.NewObjectResolver(kubeClient, cm)
			reader := vulnerabilityreport.NewReadWriter(&objectResolver)
			items, err := reader.FindByOwnerInHierarchy(ctx, workload)
			if err != nil {
				return fmt.Errorf("list vulnerability reports: %w", err)
			}
			if len(items) == 0 {
				fmt.Fprintf(out, "No reports found in %s namespace.\n", workload.Namespace)
				return nil
			}

			format := cmd.Flag("output").Value.String()
			container := cmd.Flag("container").Value.String()

			var printer printers.ResourcePrinter

			switch format {
			case "yaml", "json":
				printer, err = genericclioptions.NewPrintFlags("").
					WithTypeSetter(starboard.NewScheme()).
					WithDefaultOutput(format).
					ToPrinter()
				if err != nil {
					return err
				}
			case "":
				printer = printers.NewTablePrinter(printers.PrintOptions{})
				if err != nil {
					return err
				}
			default:
				return fmt.Errorf("invalid output format %q, allowed formats are: yaml,json", format)
			}

			list := &v1alpha1.VulnerabilityReportList{
				Items: []v1alpha1.VulnerabilityReport{},
			}

			for _, item := range items {
				if container != "" && item.Labels[starboard.LabelContainerName] != container {
					continue
				}
				list.Items = append(list.Items, item)
			}
			if len(items) > 0 && len(list.Items) == 0 {
				return fmt.Errorf("container %s is not valid for %s %s", container, strings.ToLower(string(workload.Kind)), workload.Name)
			}

			return printer.PrintObj(list, out)
		},
	}

	cmd.PersistentFlags().StringP("container", "c", "", "Get vulnerability report of this container")

	return cmd
}
